package cn.tedu.charging.cost.service.impl;

import cn.tedu.charging.common.pojo.param.ProgressCostParam;
import cn.tedu.charging.common.pojo.vo.ProgressCostVO;
import cn.tedu.charging.cost.mapper.CostRuleMapper;
import cn.tedu.charging.cost.pojo.po.CostRulePO;
import cn.tedu.charging.cost.service.CostService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;

@Service
@Slf4j
public class CostServiceImpl implements CostService {
    @Autowired
    private CostRuleMapper costRuleMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Override
    public ProgressCostVO calculateBill(ProgressCostParam param) {
        log.debug("开始计算订单金额,参数:{}",param);
        //1.获取时间小时数 比如7:59:58~7 13:14:15~13
        Integer hour=getCurrentHour();
        log.debug("当前时间小时数:{}",hour);
        //2.根据入参条件stationId gunType hour查询计价规则
        CostRulePO costRule=getCostRule(param.getStationId(),hour,1);
        //3.根据参数入参,计算本次充电单次度数
        BigDecimal chargingCapacity=calculateOnceCapacity(param.getTotalCapacity(),param.getOrderNo());
        //4.根据规则中单价和本次充电单次度数,计算总金额
        BigDecimal totalCost=calculateTotalCost(costRule.getPowerFee(),chargingCapacity,param.getOrderNo());
        //5.用上述步骤封装vo返回
        ProgressCostVO vo=new ProgressCostVO();
        vo.setChargingCapacity(chargingCapacity.doubleValue());
        vo.setPowerFee(costRule.getPowerFee().doubleValue());
        vo.setTotalCost(totalCost.doubleValue());
        return vo;
    }
    private BigDecimal calculateTotalCost(BigDecimal powerFee, BigDecimal chargingCapacity, String orderNo) {
        /**
         * 第一次 1元单价powerfee 5读单次度数chargingcapacity 计算 lasttotalCost(第一次是0)  + 本次金额5元=5
         * 第二次 1元单价powerfee 5读单次度数chargingcapacity 计算 lasttotalCost(5元)  + 本次金额5元=10
         * 这种对totalCost的累加过程可以使用redis的
         * incrby key value 会从0开始不断累加
         */
        ValueOperations<String,Double> valueOps = redisTemplate.opsForValue();
        //1.准备key值 每个订单 总金额是一个唯一值
        String key="charging:cost:total:"+orderNo;
        //2.使用单价乘以 本次度数 在bigDecimal类api是multiply
        BigDecimal onceCost=powerFee.multiply(chargingCapacity);//金额最终在取小数点
        //3.对key做累加
        Double totalCost = valueOps.increment(key, onceCost.doubleValue());
        //最终取2位小数点 四舍五入
        return new BigDecimal(totalCost).setScale(2, BigDecimal.ROUND_HALF_UP);
    }
    private BigDecimal calculateOnceCapacity(Double totalCapacity, String orderNo) {
        //getset属于string的redis数据结构 先拿到valueOps
        ValueOperations<String,Double> valueOps = redisTemplate.opsForValue();
        //1.组织一个key值,和订单绑定,每张订单都有自己唯一的一套计价过程
        String key="charging:cost:capacity:"+orderNo;
        //从redis获取 getset key totalCapacity
        Double lastTotalCapacity = valueOps.getAndSet(key, totalCapacity);
        //判断是否为空
        if (lastTotalCapacity==null){
            //2.如果为空 对上次总度数赋值0
            log.debug("这是第一次充电,没有上一次的记录,本次充电度数为:{}",totalCapacity);
            lastTotalCapacity=0.0d;
        }
        //3.使用本次度数 减去上次的度数就是单次度数 减法方法subtract
        BigDecimal chargingCapacity=new BigDecimal(totalCapacity+"").subtract(new BigDecimal(lastTotalCapacity+""));
        return chargingCapacity;
    }

    private CostRulePO getCostRule(Integer stationId, Integer hour, Integer gunType) {
        ListOperations<String,CostRulePO> listOps = redisTemplate.opsForList();
        //1.读缓存
        //1.1组织一个对应场站的key
        String key="charging:cost:rules:"+stationId;
        log.debug("从redis读取key:{}",key);
        //1.2从redis把这个场站下的所有规则全部读取 lrange key 0 -1
        List<CostRulePO> costRulePOS = listOps.range(key, 0, -1);
        //判断命中
        if (costRulePOS==null||costRulePOS.size()==0) {
            log.debug("缓存中没有数据");
            //2.没命中 还是使用方案2的代码查询数据库
            QueryWrapper<CostRulePO> qw=new QueryWrapper<>();
            qw.eq("station_id",stationId);
            costRulePOS = costRuleMapper.selectList(qw);
            //3.回填到缓存 lpush key member1 member2 member3..
            listOps.leftPushAll(key,costRulePOS);
        }
        //4.筛选list中的满足条件的数据
        return costRulePOS.stream().filter(
                    //costReulePO就是列表的中的每一个元素
                    costRulePO->
                        //对每个元素做一个逻辑表达式判断 满足逻辑表达式的元素将会被留下来
                        costRulePO.getGunType().equals(gunType)
                        && costRulePO.getStartTime()<=hour
                        && costRulePO.getEndTime()>hour)
                .findFirst()
                .orElse(null);
    }

    /*private CostRulePO getCostRule(Integer stationId, Integer hour, Integer gunType) {
        *//**
         * name   start  end  power_fee gun_type station_id
         * 尖上午   08     12     1.50   快      5     #场站id是5,时间段[08,12) 按照1.5元/度
         * 尖下午   14     18     1.50   快      5     #场站id是5,时间段[14,18) 按照1.5元/度
         * 全天峰   18     24     1.40   快      5     #场站id是5,时间段[18,24) 按照1.4元/度
         * 全天平   12     14     1.30   快      5     #场站id是5,时间段[12,14) 按照1.3元/度
         * 全天谷   00     08     1.00   快      5     #场站id是5,时间段[00,08) 按照1.0元/度
         * 全天时   00     24     1.00   快      6     #场站id是6,时间段[00,24) 按照1.0元/度
         *//*
        //方案2:
        //1.先试用stationId select * from cost_rule where station_id=5
        QueryWrapper<CostRulePO> qw=new QueryWrapper<>();
        qw.eq("station_id",stationId);
        List<CostRulePO> costRulePOS = costRuleMapper.selectList(qw);
        //2.从众多规则中 筛选gunType=1 并且 start_time<= hour 并且 end_time>hour的规则
        //使用流过滤,筛选满足条件的唯一po规则
        return costRulePOS.stream().filter(
                //costReulePO就是列表的中的每一个元素
                costRulePO->
                //对每个元素做一个逻辑表达式判断 满足逻辑表达式的元素将会被留下来
                costRulePO.getGunType().equals(gunType)
                        && costRulePO.getStartTime()<=hour
                        && costRulePO.getEndTime()>hour)
                .findFirst()
                .orElse(null);
    }*/

    /*private CostRulePO getCostRule(Integer stationId, Integer hour, Integer gunType) {
        *//**
         * name   start  end  power_fee gun_type station_id
         * 尖上午   08     12     1.50   快      5     #场站id是5,时间段[08,12) 按照1.5元/度
         * 尖下午   14     18     1.50   快      5     #场站id是5,时间段[14,18) 按照1.5元/度
         * 全天峰   18     24     1.40   快      5     #场站id是5,时间段[18,24) 按照1.4元/度
         * 全天平   12     14     1.30   快      5     #场站id是5,时间段[12,14) 按照1.3元/度
         * 全天谷   00     08     1.00   快      5     #场站id是5,时间段[00,08) 按照1.0元/度
         * 全天时   00     24     1.00   快      6     #场站id是6,时间段[00,24) 按照1.0元/度
         *//*
        //方案1: 使用条件直接查询 select * from cost_rule where station_id=5 and gun_type=1 and
        //start_time<=hour and end_time>hour
        QueryWrapper<CostRulePO> qw=new QueryWrapper<>();
        qw.eq("station_id",stationId)
                .eq("gun_type",gunType)
                .le("start_time",hour)//less than and equal
                .gt("end_time",hour);//greater than
        return costRuleMapper.selectOne(qw);
    }*/

    private Integer getCurrentHour() {
        //方案1: LocalDateTime 这一套 获取24小时制的小时数
        return LocalDateTime.now().getHour();
        //方案2:Calendar
        //Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
        //方案3: Date
        //new Date().getHours();
    }
}
